//
// Created by Administrator on 2021/11/22.
//

#include "Application.h"
#include "MeshUtility.h"
#include "ShaderProgram.h"
#include "Camera.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

// 后处理-颜色反相
#define POST_PROCESSING_INVERSION
#undef POST_PROCESSING_INVERSION
// 后处理-灰度
#define POST_PROCESSING_GRAYSCALE
#undef POST_PROCESSING_GRAYSCALE
// 后处理-锐化
#define POST_PROCESSING_SHARPEN
#undef POST_PROCESSING_SHARPEN
// 后处理-模糊
#define POST_PROCESSING_BLUR
#undef POST_PROCESSING_BLUR
// 后处理-边缘检测
#define POST_PROCESSING_EDGE_DETECTION

class App : public Application
{
    struct PerDrawBuffer
    {
    public:
        Mat4x4f viewMatrix;
        Mat4x4f projMatrix;
        Vec3f cameraPos;
    };

    struct PerObject
    {
    public:
        Mat4x4f modelMatrix;
    };

public:
    App() : Application("FrameBuffer", 800, 600) {}

    void OnCreate() override
    {
        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);

        // cube
        Vertex* vertices = nullptr;
        GLuint* indices = nullptr;
        int vertexCount, indexCount;
        MeshUtility::GenerateCube(1.0f, vertices, indices, vertexCount, indexCount);
        m_boxSp = ShaderProgram("../../FrameBuffer/shaders/box_vs.glsl",
                                "../../FrameBuffer/shaders/box_fs.glsl");
        m_camera.Setup(
                float(800) / 600,
                60.0f,
                1.0f,
                100.0f);
        m_camera.transform().position() = Vec3f(0.0f, 0.0f, 10.0f);

        glCreateTextures(GL_TEXTURE_2D, 1, &m_boxTex);
        glBindTextureUnit(0, m_boxTex);
        stbi_set_flip_vertically_on_load(true);
        int width, height, channels;
        stbi_uc* data = stbi_load("../../FrameBuffer/textures/diffuse.png", &width, &height, &channels, 0);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        stbi_image_free(data);

        glCreateVertexArrays(1, &m_boxVAO);
        glBindVertexArray(m_boxVAO);

        glCreateBuffers(1, &m_boxVBO);
        glBindBuffer(GL_ARRAY_BUFFER, m_boxVBO);
        glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(Vertex), vertices, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(2 * sizeof(Vec3f)));
        glEnableVertexAttribArray(2);

        glCreateBuffers(1, &m_boxEBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_boxEBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(GLuint), indices, GL_STATIC_DRAW);

        glCreateBuffers(1, &m_perDrawUBO);
        glNamedBufferData(m_perDrawUBO, sizeof(PerDrawBuffer), nullptr, GL_STATIC_DRAW);
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_perDrawUBO);

        glCreateBuffers(1, &m_perObjUBO);
        // 存两个
        glNamedBufferData(m_perObjUBO, 2 * sizeof(PerObject), nullptr, GL_STATIC_DRAW);
        glBindBufferBase(GL_UNIFORM_BUFFER, 1, m_perObjUBO);

        glCreateFramebuffers(1, &m_fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        glCreateTextures(1, GL_TEXTURE_2D, &m_colorBuffer);
        glBindTexture(GL_TEXTURE_2D, m_colorBuffer);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glGenerateMipmap(GL_TEXTURE_2D);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0);
        // 纹理类型的帧缓冲附件
//        GLuint dsBuffer;
//        glCreateTextures(1, &dsBuffer);
//        glBindTexture(GL_TEXTURE_2D);
//        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
//        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL, GL_TEXTURE_2D, dsBuffer, 0);

        // 如果不需要读取附件，可以使用渲染缓冲对象，原生渲染格式存储，速度非常快
        GLuint rbo;
        glCreateRenderbuffers(1, &rbo);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,  GL_RENDERBUFFER, rbo);

        if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        {
            ERROR("Framebuffer is not complete!");
        }

        // 全屏的面片
        m_quadSp = ShaderProgram("../../FrameBuffer/shaders/quad_vs.glsl",
#if defined(POST_PROCESSING_INVERSION)
                                 "../../FrameBuffer/shaders/quad_inversion_fs.glsl");
#elif defined(POST_PROCESSING_GRAYSCALE)
                                    "../../FrameBuffer/shaders/quad_gray_fs.glsl");
#elif defined(POST_PROCESSING_SHARPEN)
                                "../../FrameBuffer/shaders/quad_sharpen_fs.glsl");
#elif defined(POST_PROCESSING_BLUR)
                                 "../../FrameBuffer/shaders/quad_blur_fs.glsl");
#elif defined(POST_PROCESSING_EDGE_DETECTION)
                                 "../../FrameBuffer/shaders/quad_edge_detection_fs.glsl");
#else
                                 "../../FrameBuffer/shaders/quad_fs.glsl");
#endif
        Vertex* quadVertices = nullptr;
        GLuint* quadIndices = nullptr;
        int quadVertexCount, quadIndexCount;
        MeshUtility::GenerateQuad(quadVertices, quadIndices, quadVertexCount, quadIndexCount);
        glCreateVertexArrays(1, &m_quadVAO);
        glBindVertexArray(m_quadVAO);
        GLuint quadEBO, quadVBO;
        glCreateBuffers(1, &quadEBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadEBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, quadIndexCount * sizeof(GLuint), quadIndices, GL_STATIC_DRAW);
        glCreateBuffers(1, &quadVBO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, quadVertexCount * sizeof(Vertex), quadVertices, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(2 * sizeof(Vec3f)));
        glEnableVertexAttribArray(2);
    }

    void OnRender() override
    {
        // 第一个pass，渲染到指定的帧缓冲上
        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_STENCIL_TEST);

        m_boxSp.Use();
        glBindTexture(GL_TEXTURE_2D, m_boxTex);

        GLubyte* ptr = static_cast<GLubyte*>(glMapNamedBuffer(m_perDrawUBO, GL_WRITE_ONLY));
        memcpy(ptr, m_camera.worldToCameraMatrix().Transposed().data(), sizeof(Mat4x4f));
        ptr += sizeof(Mat4x4f);
        memcpy(ptr, m_camera.projectionMatrix().Transposed().data(), sizeof(Mat4x4f));
        ptr += sizeof(Mat4x4f);
        memcpy(ptr, &m_camera.transform().position(), sizeof(Vec3f));
        glUnmapNamedBuffer(m_perDrawUBO);

        ptr = static_cast<GLubyte*>(glMapNamedBuffer(m_perObjUBO, GL_WRITE_ONLY));
        // 第一个
        Mat4x4f modelMatrix1 = Mat4x4f(3.0f, 0.0f, 0.0f, -3.0f,
                                       0.0f, 3.0f, 0.0f, 1.0f,
                                       0.0f, 0.0f, 3.0f, 2.0f,
                                       0.0f, 0.0f, 0.0f, 1.0f);
        memcpy(ptr, modelMatrix1.Transposed().data(), sizeof(Mat4x4f));
        ptr += sizeof(Mat4x4f);
        // 第二个
        Mat4x4f modelMatrix2 = Mat4x4f(2.0f, 0.0f, 0.0f, 4.0f,
                                       0.0f, 2.0f, 0.0f, -2.0f,
                                       0.0f, 0.0f, 2.0f, -2.0f,
                                       0.0f, 0.0f, 0.0f, 1.0f);
        memcpy(ptr, modelMatrix2.Transposed().data(), sizeof(Mat4x4f));
        glUnmapNamedBuffer(m_perObjUBO);

        glBindVertexArray(m_boxVAO);
        glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, nullptr, 2);

        // 第二个pass
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_STENCIL_TEST);
        m_quadSp.Use();
        glBindTexture(GL_TEXTURE_2D, m_colorBuffer);
        glBindVertexArray(m_quadVAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    }

    void OnDestroy() override
    {
        glDeleteVertexArrays(1, &m_boxVAO);
        glDeleteBuffers(1, &m_boxVBO);
        glDeleteTextures(1, &m_boxTex);
    }

private:
    GLuint m_boxVAO;
    GLuint m_boxVBO;
    GLuint m_boxEBO;
    GLuint m_boxTex;
    GLuint m_perDrawUBO;
    GLuint m_perObjUBO;
    GLuint m_fbo;
    GLuint m_colorBuffer;
    GLuint m_quadVAO;
    ShaderProgram m_boxSp;
    ShaderProgram m_quadSp;
    Camera m_camera;
};

int main()
{
    App().Run();
    return 0;
}